/* Copyright (c) 2019 285424336@qq.com.
 * All rights reserved.
 * Author: 285424336
 * License: LGPLV3
 */
#include <glib.h>
#include <gio/gio.h>

#include <memory>

#ifdef G_OS_UNIX
#include <unistd.h>
#else
#include <gio/gwin32inputstream.h>
#include <windows.h>
#ifdef USE_VLD
#include <vld.h>
#endif
#endif

//#define USE_GAPPLICATION

static std::shared_ptr<GMainLoop> main_loop;

static void async_ready_callback(GObject *object, GAsyncResult *async_result, gpointer pointer);

#ifdef USE_GAPPLICATION
static int command_line(GApplication* application, GApplicationCommandLine *cmd_line);
#endif

/**
 * @brief main 程序入口函数
 * @param argc 启动参数个数
 * @param argv 启动参数数组
 * @return 程序返回值
 */
int main(int argc, char** argv) {
    (void)argc;
    (void)argv;
#ifdef USE_GAPPLICATION
    // 创建一个GApplication实例。
    // 如果想返回非空，则application_id必须是有效的。
    // 请参考g_application_id_is_valid()。
    // 如果application_id没有给定，则GApplication的一些特性(最显著的单例应用程序)将关闭。
    std::shared_ptr<GApplication> application(g_application_new("org.glib.console",
                                                                G_APPLICATION_HANDLES_COMMAND_LINE),
                                              [](GApplication* application) {
        if (nullptr != application) {
            // 对象引用计数减1。
            // 当引用计数到达0时，对象就结束了(比方说： 它的内存被释放了)。
            // 如果指向GObject的指针将来会被使用(例如，假如它是另一个对象的实例)，
            // 推荐清空指针置为nullptr，而不是保留一个野指针指向一个无效的GObject实例。
            // 这种情况下使用g_clear_object()。
            g_object_unref(static_cast<gpointer>(application));
        }
    });
    g_assert_nonnull(application.get());

    // 给特定对象的信号连接一个GCallback函数。
    // 该句柄将在信号默认句柄前调用。
    // 更多细节参考信号句柄内存管理中关于怎么样处理返回值和数据内存管理。
    g_signal_connect(application.get(),
                     "command-line",
                     G_CALLBACK(command_line),
                     nullptr);

    // 运行应用程序。
    // 该函数意在从main()运行并且它的返回值作为main()的返回值。
    // 尽管你期望从main()传递argc，argv到该函数，
    // 但如果argv是无效的或命令行处理不需要，传nullptr也是可以的。
    // 在windows请注意，argc和argv是被忽略的，并且g_win32_get_command_line()将在内部调用(为了适当支持unicode命令参数)。
    // GApplication将尝试解析命令行参数。
    // 你可以通过g_application_add_main_option_entries()方法添加命令行标志到已识别选项列表中。
    // 在这之后，“handle-local-options”信号将被发送，应用程序可以从GOptionEntrys检查相应值。
    // “handle-local-options”是个好的处理方式，例如--version，需要期望从本地进程立即回复(而不是和已经运行的实例通信)。
    // 一个“handle-local-options”句柄可以通过返回一个非负数来停止后续处理，之后该值将变成进程退出状态。
    // 接下来如何运行将依赖于flags:
    // 如果G_APPLICATION_HANDLES_COMMAND_LINE被指定，当"command-line"信号发送时，那么剩余命令行参数将发送到主实例。
    // 否则，剩余命令行参数将被假设为文件列表。
    // 如果命令行参数没有文件列表，应用程序将被通过“activate”信号激活。
    // 如果命令行参数里有一个或者多个文件，并且G_APPLICATION_HANDLES_OPEN被指定，之后温江通过“open”信号被打开。
    // 如果你感兴趣于处理更复杂的本地命令行处理，你应该实现你自己的GApplication子类并重写local_command_line()。
    // 在这种情况下，你最希望从你的local_command_line()实现中返回TREU来禁止默认处理句柄。
    // 请参考gapplication-example-cmdline2.c例子。
    // 假如，上边都运行完了，GApplication应用使用计数为0之后的退出状态将被立即返回。
    // 如果默认GApplication使用计数非零，则对其迭代事件直至降为零，此时该返回0。
    // 如果G_APPLICATION_IS_SERVICE被指定，之后服务将运行多达10秒并使使用计数为零，同时等待激活消息到来。
    // 之后，如果使用计数降为零，应用程序将立即退出。
    // 在使用g_application_set_inactivity_timeout()的情况下除外。
    // 该函数设置应用程序名字(g_set_prgname())，如果没有使用，那么基础名字为argv[0]。
    // 就像g_main_loop_run()，该函数将在程序运行期间获取GMainContext。
    // 从2.40版本开始，如果命令行给了"--gapplication-service"，应用未被明确标志为服务或启动
    // (换句话说：既没有指定G_APPLICATION_IS_SERVICE也没有指定G_APPLICATION_IS_LAUNCHER)
    // 将被检查(从默认句柄local_command_line开始)。
    // 如果存在此标志，那么普通的命令行处理将被中断并且G_APPLICATION_IS_SERVICE被设置。
    // 这提供了一种与从命令行直接运行程序执行它(作为调试的有用办法)一致的"折中"解决方法，而它仍旧允许应用程序被D-Bus作为服务模式激活。
    // D-Bus服务文件应该以"--gapplication-service"作为唯一的命令参数调用可执行程序。
    // 这种方法适用于大多数图形应用，但不适用于像编辑器那种需要通过命令行精确控制程序退出和退出状态的程序。
    return g_application_run(application.get(), argc - 1, argv + 1);
#else
    main_loop.reset(g_main_loop_new(nullptr, FALSE), [](GMainLoop* loop) {
        if (nullptr != loop) {
            g_main_loop_unref(loop);
        }
    });
    g_assert_nonnull(main_loop.get());
    GInputStream* input_stream = nullptr;
#ifdef G_OS_UNIX
    input_stream = g_unix_input_stream_new(STDIN_FILENO, FALSE);
#else
    input_stream = g_win32_input_stream_new(GetStdHandle(STD_INPUT_HANDLE), FALSE);
#endif
    if (nullptr == input_stream) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "maybe doesn't work on your platforms.");
        return EXIT_FAILURE;
    }
    g_assert(G_IS_INPUT_STREAM(input_stream));
    GDataInputStream* data_input_stream = g_data_input_stream_new(input_stream);
    g_assert_nonnull(data_input_stream);
    g_data_input_stream_read_line_async(data_input_stream,
                                        G_PRIORITY_DEFAULT,
                                        nullptr,
                                        async_ready_callback,
                                        static_cast<gpointer>(main_loop.get()));
    g_object_unref(data_input_stream);
    g_object_unref(input_stream);
    g_main_loop_run(main_loop.get());
    return EXIT_SUCCESS;
#endif
}

/**
 * @brief async_ready_callback 异步读就绪回调函数
 * @param object 异步就绪对象
 * @param async_result 异步函数结果
 * @param pointer 数据指针
 */
static void async_ready_callback(GObject *object, GAsyncResult *async_result, gpointer pointer) {
    GDataInputStream* data_input_stream = nullptr;
#ifdef USE_GAPPLICATION
    data_input_stream = static_cast<GDataInputStream*>(pointer);
    g_assert(data_input_stream == G_DATA_INPUT_STREAM(object));
#else
    data_input_stream = G_DATA_INPUT_STREAM(object);
    g_assert_nonnull(data_input_stream);
#endif
    gsize length = 0;
    GError* error = nullptr;
    gchar* str = nullptr;
    // 完成g_data_input_stream_read_line_async()发起的异步调用。
    // 请注意在g_data_input_stream_read_line()中关于string编码的警告也同样使用于此。
    // 行里面以NUL结尾的字节数据将被读入(不包含新行)。
    // 设置在length为读入行的长度。
    // 当出现错误时，它将返回nullptr并且错误码被设置。
    // 如果没有内容可读，它仍旧返回nullptr，但错误码不会被设置。
    str = g_data_input_stream_read_line_finish(data_input_stream,
                                               async_result,
                                               &length,
                                               &error);


    if (nullptr == str) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr == str\n");
        if (nullptr != error) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
            // 释放GError和其关联的资源
            g_error_free(error);
            error = nullptr;
        } else {
            g_warning("nullptr == error\n");
        }
#ifdef USE_GAPPLICATION
            // 返回该进程的默认GApplication实例
            // 通常每个程序只有一个GApplication，并且它是被创建的时候就成为了默认的。
            // 你可以使用g_application_set_default()来进行更多控制。
            // 如果没有默认的GApplication，那么将返回nullptr
            GApplication* application = g_application_get_default();
            g_assert_nonnull(application);
            // 立即退出应用程序。
            // 当运行在g_application_run()上时，g_application_run()将返回，返回前只会调用'shutdown'函数。
            // 持有计数将被忽略。
            // 返回后再次调用g_application_run()结果是未确定的。
            g_application_quit(application);
#else
            GMainLoop* main_loop = static_cast<GMainLoop*>(pointer);
            g_assert_nonnull(main_loop);
            g_main_loop_quit(main_loop);
#endif
    } else {
        if (strlen(str) != length) {
            g_warning("strlen(str) != length\n");
        }
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "str = %s\n", str);
        // 释放指针指向的内存。
        // 如果指向内存为空它就直接返回，所以调用此函数钱并不需要检查指向内存是否为空。
        g_free(static_cast<gpointer>(str));
        // g_data_input_stream_read_line()的异步版本。
        // 调用这个函数有两个出口是错误的。
        // 当操作完成时，callback函数将被调用。
        // 你之后可以调用g_data_input_stream_read_line_finish()获取操作结果。
        g_data_input_stream_read_line_async(data_input_stream,
                                            G_PRIORITY_DEFAULT,
                                            nullptr,
                                            async_ready_callback,
                                            static_cast<gpointer>(data_input_stream));
    }
    g_main_loop_quit(main_loop.get());
}

#ifdef USE_GAPPLICATION
/**
 * @brief command_line 命令行回调函数
 * @param application 核心应用程序实例
 * @param cmd_line 应用程序命令行调用对象
 * @return 命令行处理返回值
 */
static int command_line(GApplication* application, GApplicationCommandLine *cmd_line) {
    g_assert_nonnull(application);
    g_assert_nonnull(cmd_line);
    GInputStream* input_stream = nullptr;
    // 获取运行程序的标准输入stdin。
    // GInputStream可以用来读取执行程序的标准输入的数据。
    // 该方法并不适用所有操作系统平台。
    // 目前，当使用能够传递文件描述符的DBus守护进程时，它才在UNIX下有用。
    // 如果stdin无效，那么nullptr将被返回。
    // 未来，也许扩展到其他平台。
    // 你必须只能在命令行程序中调用这个函数一次。
    input_stream = g_application_command_line_get_stdin(cmd_line);
    if (nullptr == input_stream) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "maybe doesn't work on your platforms.");
        return EXIT_FAILURE;
    }
    // 增加GApplication使用计数。
    // 使用该方法指示应用程序出于某种原因需要继续运行GApplication。
    // 例如，当顶层窗口显示在屏幕上时GTK+调用g_application_hold()。
    // 释放保持，调用g_application_release()。
    g_application_hold(application);
    g_assert(G_IS_INPUT_STREAM(input_stream));
    // 为GInputStream创建一个新的数据输入流
    GDataInputStream* data_input_stream = g_data_input_stream_new(input_stream);
    g_assert_nonnull(data_input_stream);
    // g_data_input_stream_read_line()的异步版本。
    // 调用这个函数有两个出口是错误的。
    // 当操作完成时，callback函数将被调用。
    // 你之后可以调用g_data_input_stream_read_line_finish()获取操作结果。
    g_data_input_stream_read_line_async(data_input_stream,
                                        G_PRIORITY_DEFAULT,
                                        nullptr,
                                        async_ready_callback,
                                        static_cast<gpointer>(data_input_stream));
    g_object_unref(data_input_stream);
    g_object_unref(input_stream);
    return EXIT_SUCCESS;
}
#endif
